﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Game_Engine
{
    public class TerrainRenderer : Renderer
    {
        private VertexPositionTexture[] Vertices { get; set; }
        private int[] Indices { get; set; }
        public float[] Heights { get; set; }
        public Vector3[] Normals { get; set; }
        public Texture2D HeightMap { get; set; }
        public Vector2 Size { get; set; }
        public override Material Material
        {
            get
            {
                return base.Material;
            }
            set
            {
                base.Material = value;
                CreateNormals();
            }
        }

        public TerrainRenderer(Texture2D heightmap, Vector2 size, Vector2 resolution)
        {
            Size = size;
            HeightMap = heightmap;
            CreateHeights();

            //for an mxn grid we need (m+1)x(n+1) vertices. 
            //These become the dimensions of our vertex array
            int rows = (int)resolution.Y + 1;
            int cols = (int)resolution.X + 1;
            Vertices = new VertexPositionTexture[rows * cols];

            //The offset shifts all of our vertices such that the position of the terrain
            //corresponds to its center. which is cool
            Vector3 offset = new Vector3(-Size.X / 2, 0, -Size.Y / 2);

            //Compute the size of cells within the gridwork
            float stepX = Size.X / resolution.X;
            float stepZ = Size.Y / resolution.Y;

            for(int r = 0; r < rows; r++)
                for(int c = 0; c < cols; c++)
                {
                    //Vector3 sets position, Vector2 sets texture coordiantes
                    //Texture coordinates are values between 0 and 1.
                    Vertices[r * cols + c] = new VertexPositionTexture(offset + 
                        new Vector3(c*stepX, 
                            GetHeight(new Vector2(c/resolution.X, r/resolution.Y)), 
                            r*stepZ), 
                        new Vector2(c / resolution.X, r / resolution.Y));
                    //Vertices[r * cols + c].Position.Y =
                        //GetHeight(Vertices[r * cols + c].TextureCoordinate);
                }

            //Here we create the indices of our triangles. There are two triangles per cell, 
            //each triangle requiring three vertices(3*2)=6. The number of cells is given by our
            //original resolution dimensions
            Indices = new int[(rows - 1) * (cols - 1) * 6];
            
            int index = 0;
            for(int r = 0; r < rows - 1; r++)
                for(int c = 0; c < cols - 1; c++)
                {
                    Indices[index++] = r * cols + c;
                    Indices[index++] = r * cols + (c + 1);
                    Indices[index++] = (r + 1) * cols + c;
                    //Triangle Winding -- Johnny says it works daddy
                    Indices[index++] = (r + 1) * cols + (c + 1);
                    Indices[index++] = (r + 1) * cols + c;
                    Indices[index++] = r * cols + (c + 1);
                }      
        }

        private void CreateHeights()
        {
            Color[] data = new Color[HeightMap.Width * HeightMap.Height];
            HeightMap.GetData<Color>(data);
            Heights = new float[HeightMap.Width * HeightMap.Height];
            for (int i = 0; i < Heights.Length; i++)
                Heights[i] = data[i].G / 255.0f;
        }

        private void CreateNormals()
        {
            Texture2D normalMap = Material.Texture;
            Color[] data = new Color[normalMap.Width * normalMap.Height];
            Vector3 normal;
            normalMap.GetData<Color>(data);
            Normals = new Vector3[normalMap.Width * normalMap.Height];
            
            //The ToVector3() method automatically converts color values to the correct range
            for (int i = 0; i < Normals.Length; i++)
            {
                normal = data[i].ToVector3();
                Normals[i] = normal;
            }
        }

        //Bilinear interpolation is pretty cool. Instead of interpolating along a line we
        //interpolate across two lines--which suits rectangles and squares like our texture just
        //fine. In fact, these two lines are defined between four pixels, which are exactly like
        //the vertices of a square/rectangle.
        public float GetHeight(Vector2 tex)//The parameter is the texture coordinate portion of
        {                                   //the VertexPositionTexture structure
            tex *= new Vector2(HeightMap.Width - 1, HeightMap.Height - 1);  //Scale by image

            //Now we have a location on the image
            //Casting our texture coordinate to integers truncates them--rounds down. The modulus
            int x = (int)tex.X; float u = tex.X % 1; //operation captures the fractional portion    
            int y = (int)tex.Y; float v = tex.Y % 1; //that is lost to permit interpolation.

            //If both texture coordinates are 1 we need only return the corner value
            if (x == HeightMap.Width - 1 && y == HeightMap.Height - 1)
                return Heights[(HeightMap.Width)*(HeightMap.Height) - 1];

            //If only one coordinate is valued at one we can do linear interpolation along an edge
            if (x == HeightMap.Width - 1)
                return Heights[y * HeightMap.Height + x] * v +              //A*u +
                        Heights[(y + 1) * HeightMap.Height + x] * (1 - v);    //B*(1-u)

            if (y == HeightMap.Height - 1)
                return Heights[y * HeightMap.Height + x] * u +     //A*v +
                        Heights[y * HeightMap.Height + x + 1] * (1 - u);       //B*(1-v) +

            return  Heights[y * HeightMap.Height + x] * (1 - u) * (1 - v) +     //A*(1-u)(1-v) +
                    Heights[y * HeightMap.Height + x + 1] * u * (1 - v) +       //B*(u)(1-v) +
                    Heights[(y + 1) * HeightMap.Height + x] * (1 - u ) * v +    //C*(u)(v) +
                    Heights[(y + 1) * HeightMap.Height + x + 1] * u * v;        //D*(1-u)(v)
                    //Now we selectively sample our heights generated from CreateHeights().
        }

        public override void Draw()
        {
            if (Material != null && Material.On)
            {
                Material.Update(Transform.World, Material.Camera,
                    Material.Light, Material.Color,  Material.Sheen,
                    Material.Offset, Material.Tiling);
                foreach (EffectPass pass in Material.Effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    ScreenManager.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>
                    (PrimitiveType.TriangleList, Vertices, 0, Vertices.Length, Indices, 0, Indices.Length / 3);
                }
            }
            
        }

    }
}
